#! /usr/bin/env python3

####################################################################################################
#
# CodeReview - A Code Review GUI
# Copyright (C) 2019 Fabrice Salvaire
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
####################################################################################################

"""Program to manage and translate Qt translation files.

It features:

* lupdate, linguist, lrelease
* conversion to/from .po file
* poedit
* translation using Google Translation

Actions are run in the right order.

"""

####################################################################################################

from pathlib import Path
import argparse
import os
import subprocess

from googletrans import Translator
# https://py-googletrans.readthedocs.io/en/latest/

####################################################################################################

DEFAULT_LANGUAGES = (
    'fr_FR',
)

# Fixme: project dependent
source_path = Path(__file__).resolve().parents[1]
qml_path = source_path.joinpath('qml')
py_path = source_path.joinpath('CodeReview')
translation_path = source_path.joinpath('share', 'translations')
base_name = 'code-review'

####################################################################################################

parser = argparse.ArgumentParser(description='Translate Qt Application.')

parser.add_argument(
    '--language',
    default=None,
    help='languages, default {}'.format(DEFAULT_LANGUAGES),
)

parser.add_argument(
    '--update',
    default=False,
    action='store_true',
    help='run lupdate',
)

parser.add_argument(
    '--translate',
    default=False,
    action='store_true',
    help='translate using Google Translate',
)

parser.add_argument(
    '--convert-to-po',
    default=False,
    action='store_true',
    help='convert to .po file',
)

parser.add_argument(
    '--convert-from-po',
    default=False,
    action='store_true',
    help='convert from .po file',
)

parser.add_argument(
    '--poedit',
    default=False,
    action='store_true',
    help='run poedit',
)

parser.add_argument(
    '--linguist',
    default=False,
    action='store_true',
    help='run linguist',
)

parser.add_argument(
    '--release',
    default=False,
    action='store_true',
    help='run lrelease',
)

args = parser.parse_args()

####################################################################################################

def _clean_path(path):
    return Path(str(path)).resolve()

####################################################################################################

class TanslationManager:

    ##############################################

    def __init__(self, translation_path, base_name, py_path, qml_path):

        self._translation_path = _clean_path(translation_path)
        self._base_name = base_name
        self._py_path = _clean_path(py_path)
        self._qml_path = _clean_path(qml_path)

        if not self._translation_path.exists():
            os.mkdir(self._translation_path)

    ##############################################

    def ts_path(self, scope, language):
        if scope:
            filename = '{}.{}.{}.ts'.format(self._base_name, scope, language)
        else:
            filename = '{}.{}.ts'.format(self._base_name, language)
        return self._translation_path.joinpath(filename)

    def po_path(self, scope, language):
        filename = '{}.{}.{}.po'.format(self._base_name, scope, language)
        return self._translation_path.joinpath(filename)

    ##############################################

    def run_lupdate(self, language):

        # Usage:
        #     lupdate [options] [project-file]...
        #     lupdate [options] [source-file|path|@lst-file]... -ts ts-files|@lst-file
        #
        # lupdate is part of Qt's Linguist tool chain. It extracts translatable
        # messages from Qt UI files, C++, Java and JavaScript/QtScript source code.
        # Extracted messages are stored in textual translation source files (typically
        # Qt TS XML). New and modified messages can be merged into existing TS files.
        #
        # Options:
        #     -help  Display this information and exit.
        #     -no-obsolete
        #            Drop all obsolete and vanished strings.
        #     -extensions <ext>[,<ext>]...
        #            Process files with the given extensions only.
        #            The extension list must be separated with commas, not with whitespace.
        #            Default: 'java,jui,ui,c,c++,cc,cpp,cxx,ch,h,h++,hh,hpp,hxx,js,qs,qml,qrc'.
        #     -pluralonly
        #            Only include plural form messages.
        #     -silent
        #            Do not explain what is being done.
        #     -no-sort
        #            Do not sort contexts in TS files.
        #     -no-recursive
        #            Do not recursively scan the following directories.
        #     -recursive
        #            Recursively scan the following directories (default).
        #     -I <includepath> or -I<includepath>
        #            Additional location to look for include files.
        #            May be specified multiple times.
        #     -locations {absolute|relative|none}
        #            Specify/override how source code references are saved in TS files.
        #            Guessed from existing TS files if not specified.
        #            Default is absolute for new files.
        #     -no-ui-lines
        #            Do not record line numbers in references to UI files.
        #     -disable-heuristic {sametext|similartext|number}
        #            Disable the named merge heuristic. Can be specified multiple times.
        #     -pro <filename>
        #            Name of a .pro file. Useful for files with .pro file syntax but
        #            different file suffix. Projects are recursed into and merged.
        #     -pro-out <directory>
        #            Virtual output directory for processing subsequent .pro files.
        #     -pro-debug
        #            Trace processing .pro files. Specify twice for more verbosity.
        #     -source-language <language>[_<region>]
        #            Specify the language of the source strings for new files.
        #            Defaults to POSIX if not specified.
        #     -target-language <language>[_<region>]
        #            Specify the language of the translations for new files.
        #            Guessed from the file name if not specified.
        #     -tr-function-alias <function>{+=,=}<alias>[,<function>{+=,=}<alias>]...
        #            With +=, recognize <alias> as an alternative spelling of <function>.
        #            With  =, recognize <alias> as the only spelling of <function>.
        #            Available <function>s (with their currently defined aliases) are:
        #              Q_DECLARE_TR_FUNCTIONS (=Q_DECLARE_TR_FUNCTIONS)
        #              QT_TR_N_NOOP (=QT_TR_N_NOOP)
        #              QT_TRID_N_NOOP (=QT_TRID_N_NOOP)
        #              QT_TRANSLATE_N_NOOP (=QT_TRANSLATE_N_NOOP)
        #              QT_TRANSLATE_N_NOOP3 (=QT_TRANSLATE_N_NOOP3)
        #              QT_TR_NOOP (=QT_TR_NOOP)
        #              QT_TRID_NOOP (=QT_TRID_NOOP)
        #              QT_TRANSLATE_NOOP (=QT_TRANSLATE_NOOP)
        #              QT_TRANSLATE_NOOP3 (=QT_TRANSLATE_NOOP3)
        #              QT_TR_NOOP_UTF8 (=QT_TR_NOOP_UTF8)
        #              QT_TRANSLATE_NOOP_UTF8 (=QT_TRANSLATE_NOOP_UTF8)
        #              QT_TRANSLATE_NOOP3_UTF8 (=QT_TRANSLATE_NOOP3_UTF8)
        #              findMessage (=findMessage)
        #              qtTrId (=qtTrId)
        #              tr (=tr)
        #              trUtf8 (=trUtf8)
        #              translate (=translate)
        #              qsTr (=qsTr)
        #              qsTrId (=qsTrId)
        #              qsTranslate (=qsTranslate)
        #     -ts <ts-file>...
        #            Specify the output file(s). This will override the TRANSLATIONS.
        #     -version
        #            Display the version of lupdate and exit.
        #     @lst-file
        #            Read additional file names (one per line) or includepaths (one per
        #            line, and prefixed with -I) from lst-file.

        command = (
            'lupdate-qt5',
            '-extensions', 'qml,js',
            '-source-language', 'en_GB',
            '-target-language', language,
            str(self._qml_path),
            '-ts', str(self.ts_path('qml', language)),
        )
        print('>', ' '.join(command))
        subprocess.check_call(command)

        py_filenames = []
        for root, _, filenames in os.walk(self._py_path):
            root = Path(root)
            for filename in filenames:
                filename = Path(filename)
                if filename.suffix == '.py':
                    filename = str(root.joinpath(filename))
                    py_filenames.append(filename)

        command = (
            'pylupdate5',
            '-verbose',
            *py_filenames,
            '-ts', str(self.ts_path('py', language)),
        )
        print('>', ' '.join(command))
        subprocess.check_call(command)

        # Fixme: pylupdate5 don't support qml and folder !!!

        # print('Fix obsolete')
        # with open(ts_path, 'r') as fh:
        #     content = fh.readlines()
        # with open(ts_path, 'w') as fh:
        #     for line in content:
        #         line = line.replace('type="obsolete"', '')
        #         line = line.replace('<translation >', '<translation>')
        #         fh.write(line)

    ##############################################

    def run_linguist(self, language):

        for scope in ('qml', 'py'):
            command = (
                'linguist-qt5',
                str(self.ts_path(scope, language)),
            )
            print('>', ' '.join(command))
            subprocess.check_call(command)

    ##############################################

    def run_poedit(self, language):

        command = (
            'poedit',
            str(self.po_path(language)),
        )
        print('>', ' '.join(command))
        subprocess.check_call(command)

    ##############################################

    def run_lrelease(self, language):

        # Usage:
        #     lrelease [options] project-file
        #     lrelease [options] ts-files [-qm qm-file]
        #
        # lrelease is part of Qt's Linguist tool chain. It can be used as a
        # stand-alone tool to convert XML-based translations files in the TS
        # format into the 'compiled' QM format used by QTranslator objects.
        #
        # Options:
        #     -help  Display this information and exit
        #     -idbased
        #            Use IDs instead of source strings for message keying
        #     -compress
        #            Compress the QM files
        #     -nounfinished
        #            Do not include unfinished translations
        #     -removeidentical
        #            If the translated text is the same as
        #            the source text, do not include the message
        #     -markuntranslated <prefix>
        #            If a message has no real translation, use the source text
        #            prefixed with the given string instead
        #     -silent
        #            Do not explain what is being done
        #     -version
        #            Display the version of lrelease and exit

        command = (
            'lconvert-qt5',
            '-i',
            str(self.ts_path('qml', language)),
            str(self.ts_path('py', language)),
            '-o', str(self.ts_path(None, language)),
        )
        print('>', ' '.join(command))
        subprocess.check_call(command)

        command = (
            'lrelease-qt5',
            str(self.ts_path(None, language)),
        )
        print('>', ' '.join(command))
        subprocess.check_call(command)

    ##############################################

    def run_lconvert(self, language, format):

        # Usage:
        #     lconvert [options] <infile> [<infile>...]
        #
        # lconvert is part of Qt's Linguist tool chain. It can be used as a
        # stand-alone tool to convert and filter translation data files.
        # The following file formats are supported:
        #
        #     qm    - Traductions Qt compilées
        #     pot   - Fichiers de modèle de localisation GNU Gettext
        #     qph   - Qt Linguist "livre de phrases"
        #     ts    - Sources de traduction Qt
        #     po    - Fichiers de localisation GNU Gettext
        #     xlf   - Fichiers de localisation XLIFF
        #
        # If multiple input files are specified, they are merged with
        # translations from later files taking precedence.
        #
        # Options:
        #     -h
        #     -help  Display this information and exit.
        #
        #     -i <infile>
        #     -input-file <infile>
        #            Specify input file. Use if <infile> might start with a dash.
        #            This option can be used several times to merge inputs.
        #            May be '-' (standard input) for use in a pipe.
        #
        #     -o <outfile>
        #     -output-file <outfile>
        #            Specify output file. Default is '-' (standard output).
        #
        #     -if <informat>
        #     -input-format <format>
        #            Specify input format for subsequent <infile>s.
        #            The format is auto-detected from the file name and defaults to 'ts'.
        #
        #     -of <outformat>
        #     -output-format <outformat>
        #            Specify output format. See -if.
        #
        #     -drop-tags <regexp>
        #            Drop named extra tags when writing TS or XLIFF files.
        #            May be specified repeatedly.
        #
        #     -drop-translations
        #            Drop existing translations and reset the status to 'unfinished'.
        #            Note: this implies --no-obsolete.
        #
        #     -source-language <language>[_<region>]
        #            Specify/override the language of the source strings. Defaults to
        #            POSIX if not specified and the file does not name it yet.
        #
        #     -target-language <language>[_<region>]
        #            Specify/override the language of the translation.
        #            The target language is guessed from the file name if this option
        #            is not specified and the file contents name no language yet.
        #
        #     -no-obsolete
        #            Drop obsolete messages.
        #
        #     -no-finished
        #            Drop finished messages.
        #
        #     -no-untranslated
        #            Drop untranslated messages.
        #
        #     -sort-contexts
        #            Sort contexts in output TS file alphabetically.
        #
        #     -locations {absolute|relative|none}
        #            Override how source code references are saved in TS files.
        #            Default is absolute.
        #
        #     -no-ui-lines
        #            Drop line numbers from references to UI files.
        #
        #     -verbose
        #            be a bit more verbose
        #
        # Long options can be specified with only one leading dash, too.
        #
        # Return value:
        #     0 on success
        #     1 on command line parse failures
        #     2 on read failures
        #     3 on write failures

        is_po = format == 'po'

        for scope in ('qml', 'py'):
            command = (
                'lconvert-qt5',
                '-i' if is_po else '-o', str(self.ts_path(scope, language)),
                '-o' if is_po else '-i', str(self.po_path(language)),
            )
            print('>', ' '.join(command))
            subprocess.check_call(command)

    ##############################################

    def translate(self, language):
        for scope in ('qml', 'py'):
            self.translate_scope(scope, language)

    ##############################################

    def translate_scope(self, scope, language):

        print('Translate {} {} ...'.format(scope, language))

        language_code = language[:2]
        if language_code == 'en':
            return

        translator = Translator(
            service_urls=[
                'translate.google.' + language_code,
                'translate.google.com',
            ],
        )

        def translate_string(source):
            return translator.translate(source, src='en', dest=language_code).text

        ts_path = self.ts_path(scope, language)
        with open(ts_path, 'r') as fh:
            lines = list(fh.readlines())

        with open(ts_path, 'w') as fh:
            source = None
            for line in lines:
                striped_line = line.strip()
                if striped_line.startswith('<source>'):
                    source = striped_line[striped_line.find('>')+1:striped_line.rfind('<')]
                elif striped_line.startswith('<translation type="unfinished">'):
                    old_translation = striped_line[striped_line.find('>')+1:striped_line.rfind('<')]
                    if not old_translation:
                        translation = translate_string(source)
                        print()
                        print(source)
                        print(translation)
                        line = line.replace(' type="unfinished">', '>' + translation)
                fh.write(line)

####################################################################################################

manager = TanslationManager(
    translation_path,
    base_name,
    py_path,
    qml_path,
)

if not args.language:
    languages = DEFAULT_LANGUAGES
else:
    languages = [x for x in [x.strip() for x in args.languages.split(',')] if x]

if args.update:
    for language in languages:
        manager.run_lupdate(language)
if args.translate:
    for language in languages:
        manager.translate(language)
if args.convert_to_po:
    for language in languages:
        manager.run_lconvert(language, 'po')
if args.poedit:
    for language in languages:
        manager.run_poedit(language)
if args.convert_from_po:
    for language in languages:
        manager.run_lconvert(language, 'ts')
if args.linguist:
    for language in languages:
        manager.run_linguist(language)
if args.release:
    for language in languages:
        manager.run_lrelease(language)
